using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace FileCollection
{
    public class FileListManager<T>
    {
        #region Variables

        private readonly Dictionary<string, StreamFileList<T>> listCollection;
        private readonly DirectoryInfo rootPath;
        [NonSerialized] private object syncRoot;

        #endregion

        #region Properties

        public string RootPath
        {
            get { return this.rootPath.FullName; }
        }

        public virtual object SyncRoot
        {
            get
            {
                if (this.syncRoot == null)
                {
                    Interlocked.CompareExchange(ref syncRoot, new object(), null);
                }
                return this.syncRoot;
            }
        }

        #endregion

        #region Constructors

        public FileListManager(string rootPath) : this(rootPath, false)
        {
        }

        public FileListManager(string rootPath, bool automaticSave) //, bool automaticRecovereQueue)
        {
            if (string.IsNullOrEmpty(rootPath))
            {
                throw new ArgumentNullException("rootPath");
            }

            this.rootPath = new DirectoryInfo(rootPath);

            if (!this.rootPath.Exists)
            {
                this.rootPath.Create();
            }

            this.listCollection = new Dictionary<string, StreamFileList<T>>();

            foreach (FileInfo fileInfo in FileCollectionBase<LinkedList<Guid>, T>.FindCollections(this.rootPath.FullName, SearchOption.AllDirectories))
            {
                if (fileInfo.Directory != null)
                {
                    AddList(fileInfo.Directory.Name, automaticSave);
                }
            }
        }

        #endregion

        #region Public Methods
        
        public int RepairAll()
        {
            lock (this.SyncRoot)
            {
                int count = 0;
                Trace.TraceInformation("FileList Manager {0} Repair All Lists Starting.", this.RootPath);
                foreach (var list in listCollection.Values)
                {
                    count += list.Repair();
                }

                Trace.TraceInformation("FileList Manager {0} Repair All Lists Finished. Errors Found: {1}", this.RootPath, count);
                return count;
            }
        }

        public int Repair(string listName)
        {
            Trace.TraceInformation("FileList Manager {0} Starting targetName Repair {1}.", this.RootPath, listName);
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            var list = this.listCollection[listName];
            int count = 0;
            if (list != null)
            {
                count = list.Repair();
            }

            Trace.TraceInformation("FileList Manager {0} Finished targetName Repair {1}. Errors found: {2}", this.RootPath, listName, count);
            return count;
        }

        public void AddList(string listName)
        {
            AddList(listName, false);
        }

        public StreamFileList<T> GetList(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            return this.listCollection[listName];
        }

        public void AddList(string listName, bool automaticSave)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }


            if (!Exists(listName))
            {
                Trace.TraceInformation("FileList Manager {0}  - Adding the list {0}", listName);
                var list = new StreamFileList<T>(ReturnListPath(listName), listName, automaticSave);
                lock (this.SyncRoot)
                {
                    this.listCollection.Add(listName, list);
                }
                Trace.TraceInformation("FileList Manager {0}  - Adding the list {0} completed", listName);
            }
        }

        public bool Exists(string listName)
        {
            return this.listCollection.ContainsKey(listName);
        }

        public bool Exists(string listName, T info)
        {
            if (string.IsNullOrEmpty(listName))
                throw new ArgumentNullException("listName");

            StreamFileList<T> list = this.listCollection[listName];

            if (list != null)
            {
                return list.Exists(info);
            }

            return false;
        }

        public bool Exists(string listName, Guid id)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.Exists(id);
                }
            }
            return false;
        }

        public void RemoveList(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }


            if (Exists(listName))
            {
                Trace.TraceInformation("FileList Manager {0}  - Removing the list {0}", listName);
                using (var list = this.listCollection[listName])
                {
                    if (list != null)
                    {
                        lock (this.SyncRoot)
                        {
                            this.listCollection.Remove(listName);
                        }

                        list.Clear();
                        list.Close();
                    }
                }

                string directoryName = this.ReturnListPath(listName);

                if (Directory.Exists(directoryName))
                {
                    Directory.Delete(directoryName, true);
                }

                Trace.TraceInformation("FileList Manager {0}  - Removing the list {0} completed.", listName);
            }
        }

        public T First(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                return list != null ? list.First : default(T);
            }
        }

        public T Last(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                return list != null ? list.Last : default(T);
            }
        }


        public Guid AddAfter(string listName, Guid id, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.AddAfter(id, stream, info);
                }
            }

            return Guid.Empty;
        }

        public Guid AddBefore(string listName, Guid id, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                     return list.AddBefore(id, stream, info);
                }
            }

            return Guid.Empty;
        }

        public T Find(string listName, Guid id)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.Find(id);
                }
            }

            return default(T);
        }

        public Guid Find(string listName, T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.Find(info);
                }
            }

            return Guid.Empty;
        }

        public void AddFirst(string listName, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                  throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    list.AddFirst(stream, info);
                }
            }
        }

        public void AddLast(string listName, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    list.AddLast(stream, info);
                }
            }
        }

        public void Enqueue(string listName, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(listName))
                throw new ArgumentNullException("listName");

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {list.Enqueue(stream, info);}
            }
        }

        public Stream Dequeue(string listName, out T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                info = default(T);

                if (list != null)
                {
                    return list.Dequeue(out info);
                }
            }

            return null;
        }


        public void RemoveFirst(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    list.RemoveFirst();
                }
            }
        }

        public void RemoveLast(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                  throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                     list.RemoveLast();
                }
            }
        }

        public void Remove(string listName, Guid id)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                     list.Remove(id);
                }
            }
        }

        public Guid DequeueToAnotherList(string sourceName, string targetName)
        {
            if (string.IsNullOrEmpty(sourceName))
            {
                throw new ArgumentNullException("sourceName");
            }

            if (string.IsNullOrEmpty(targetName))
            {
                throw new ArgumentNullException("targetName");
            }

            using (var sourceList = this.listCollection[sourceName])
            {
                using (var targetList = this.listCollection[targetName])
                {
                    if (sourceList != null && targetList != null)
                    {
                        return sourceList.DequeueToAnotherList(targetList);
                    }
                }
            }

            return Guid.Empty;
        }

        /// <summary>
        /// Get a collection with the name of the l
        /// </summary>
        /// <returns></returns>
        public string[] GetListNames()
        {
            lock (this.SyncRoot)
            {
                var result = new string[this.listCollection.Count];
                int i = 0;
                foreach (string listName in this.listCollection.Keys)
                {
                    result[i++] = listName;
                }

                return result;
            }
        }

        public Guid DequeueToAnotherList(string sourceListName, StreamFileList<T> targetList)
        {
            if (string.IsNullOrEmpty(sourceListName))
            {
                throw new ArgumentNullException("sourceListName");
            }

            if (targetList == null)
            {
                throw new ArgumentNullException("targetList");
            }

            using (var fromList = this.listCollection[sourceListName])
            {
                if (fromList != null)
                {
                    return fromList.DequeueToAnotherList(targetList);
                }
            }

            return Guid.Empty;
        }

        /// <summary>
        ///   Clear all items sourceName a queue
        /// </summary>
        /// <param name="listName">Queue Name targetName Clear all items</param>
        public void Clear(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    list.Clear();
                }
            }
        }

        public void ClearAll()
        {
            lock (SyncRoot)
            {
                foreach (var list in listCollection.Values)
                {
                    list.Clear();
                }
            }
        }


        public void Close()
        {
            lock (this.SyncRoot)
            {
                foreach (var list in listCollection.Values)
                {
                    list.Close();
                }
            }
        }

        public void Close(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = listCollection[listName])
            {
                lock (this.SyncRoot)
                {
                    this.listCollection.Remove(listName);
                }

                if (list != null)
                {
                    list.Close();
                }
            }
        }

        public T[] GetInformationList(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = listCollection[listName])
            {
                return list != null ?  list.GetInformationList() : default(T[]);
            }
        }

        public ExtraInformation<T>[] GetExtraInformationList(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                return list != null
                           ? list.GetExtraInformationList()
                           : default(ExtraInformation<T>[]);
            }
        }

        public Stream GetItem(string listName, Guid id, out T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.GetItem(id, out info);
                }
            }

            info = default(T);
            return null;
        }

        public Stream GetFirst(string listName, out T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                 throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                    return list.GetFirst(out info);
                }
            }

            info = default(T);
            return null;
        }

        public Stream GetLast(string listName, out T info)
        {
            if (string.IsNullOrEmpty(listName))
            {
                  throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                if (list != null)
                {
                      return list.GetLast(out info);
                }
            }

            info = default(T);
            return null;
        }

        public int GetCount(string listName)
        {
            if (string.IsNullOrEmpty(listName))
            {
                throw new ArgumentNullException("listName");
            }

            using (var list = this.listCollection[listName])
            {
                return list != null ? list.Count : -1;
            }
        }

        #endregion

        #region Private Methods

        private string ReturnListPath(string listName)
        {
            return Path.Combine(this.rootPath.FullName, listName);
        }

        #endregion
    }
}